﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Adaptive_Clustering
{
    class KMeans
    {
        Byte[,] ImageArray;
        Byte[,] ImageClusterIndexArray;
        
        MainGUI M_GUI;

        public Byte[,] GetImageArray() { return ImageArray; }
        
        public Byte[,] GetImageClusterIndexArray() { return ImageClusterIndexArray; }

        public KMeans(Byte[,] ImageArray, int k, MainGUI M_GUI) 
        {
            Byte[] ClustersCentersArray;
            this.M_GUI = M_GUI;
            this.ImageClusterIndexArray = RunKMeans(ImageArray, k); //find image regions (#k)
            ClustersCentersArray = AverageClusters(ImageClusterIndexArray, ImageArray, k); //find the GrayScale of each region
            this.ImageArray = ClusterizePixels(ImageClusterIndexArray, ClustersCentersArray); //change each pixel to its region GrayScale color
        }

        private Byte[,] RunKMeans(Byte[,] ImageArray, int k)
        {
            double ProgressRatio = (double)(Constants.ProgressBarK_means / ImageArray.GetLength(0));
            double ProgressCounter = M_GUI.ProcessImageProgressBar % Constants.ProgressBarMaxVal;

            int imageWidth = ImageArray.GetLength(0);
            int imageHeight = ImageArray.GetLength(1);
            Byte[,] ImageClusterIndexArray = new Byte[imageWidth, imageHeight];
            Byte[] ClustersCentersArray = new Byte[k];
            double modu = 256 / (double)k;
            int j = 0;
            for (int i = (int)(modu / 2); i < 256 && j < k; i += (int)modu, j++)
                ClustersCentersArray[j] = (Byte)i;
            for (int x = 0; x < imageWidth; x++)
            {
                for (int y = 0; y < imageHeight; y++)
                    ImageClusterIndexArray[x, y] = ClosestDistance(ClustersCentersArray, ImageArray[x, y], k);
                ProgressCounter += ProgressRatio;
                M_GUI.ProcessImageProgressBar = (int)(ProgressCounter % Constants.ProgressBarMaxVal);
            }
            return ImageClusterIndexArray;
        }

        private Byte[,] ClusterizePixels(Byte[,] ImageClusterIndexArray, Byte[] ClustersCentersArray)
        {
            Byte[,] ImageArray = new Byte[ImageClusterIndexArray.GetLength(0), ImageClusterIndexArray.GetLength(1)];
            for (int x = 0; x < ImageClusterIndexArray.GetLength(0); x++)
                for (int y = 0; y < ImageClusterIndexArray.GetLength(1); y++)
                {
                    ImageArray[x, y] = ClustersCentersArray[ImageClusterIndexArray[x, y]];
                }
            return ImageArray;
        }

        private Byte[] AverageClusters(Byte[,] ImageClusterIndexArray, Byte[,] ImageArray, int k)
        {
            double ProgressRatio = (double)(Constants.ProgressBarAverageClusters / ImageArray.GetLength(0));
            double ProgressCounter = M_GUI.ProcessImageProgressBar % Constants.ProgressBarMaxVal;
            
            UInt64[] sum = new UInt64[k];
            UInt64[] count = new UInt64[k];

            Byte[] ClustersCentersArray = new Byte[k];
            for (int i = 0; i < k; i++)
            {
                sum[i] = 0;
                count[i] = 0;
            }

            for (int x = 0; x < ImageClusterIndexArray.GetLength(0); x++)
            {
                for (int y = 0; y < ImageClusterIndexArray.GetLength(1); y++)
                {
                    count[ImageClusterIndexArray[x, y]]++;
                    sum[ImageClusterIndexArray[x, y]] += ImageArray[x, y];
                }
                ProgressCounter += ProgressRatio;
                M_GUI.ProcessImageProgressBar = (int)(ProgressCounter % Constants.ProgressBarMaxVal);
            }
            for (int i = 0; i < k; i++)
                if (count[i] != 0)
                    ClustersCentersArray[i] = (Byte)(sum[i] / count[i]);

            return ClustersCentersArray;
        }

        private Byte ClosestDistance(Byte[] ClustersArray, Byte Pixel, int k)
        { //find for each pixel the region most close to it
            Byte ClosestIndex = 0, Distance = 255;
            int TempDistance;
            for (int i = 0; i < k; i++)
            {
                TempDistance = Math.Abs(ClustersArray[i] - Pixel);
                if (TempDistance < Distance)
                {
                    Distance = (Byte)TempDistance;
                    ClosestIndex = (Byte)i;
                }
            }
            return ClosestIndex;
        }
    }
}
